package com.yeswater.redislock.service;

import cn.hutool.core.util.IdUtil;
import com.yeswater.redislock.constant.Constant;
import com.yeswater.redislock.lock.DistributedLockFactory;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j
public class InventoryService {
	@Autowired
	private StringRedisTemplate stringRedisTemplate;
	@Value("${server.port}")
	private String port;
	
	private final Lock lock = new ReentrantLock();
	
	@Resource
	private DistributedLockFactory distributedLockFactory;
	
	/**
	 * 测试lock 锁单机 & lock 锁集群
	 */
	public String sale1() {
		String retMessage = "";
		lock.lock();
		try {
			//1 查询库存信息
			String result = stringRedisTemplate.opsForValue().get(Constant.RedisKey.INVENTORY001);
			//2 判断库存是否足够
			int inventoryNumber = result == null ? 0 : Integer.parseInt(result);
			//3 扣减库存
			if (inventoryNumber > 0) {
				stringRedisTemplate.opsForValue().set(Constant.RedisKey.INVENTORY001, String.valueOf(--inventoryNumber));
				retMessage = "成功卖出一个商品，库存剩余: " + inventoryNumber;
				System.out.println(retMessage);
			} else {
				retMessage = "商品卖完了，o(╥﹏╥)o";
			}
		} finally {
			lock.unlock();
		}
		return retMessage + "\t" + "服务端口号：" + port;
	}
	
	/**
	 * 分布式锁：setnx + 递归重试
	 */
	public String sale2() {
		String retMessage = "";
		String key = "RedisLock";
		String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
		
		// setnx <key, value>
		Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);
		if (!flag) {
			//暂停20毫秒后递归调用
			try {
				TimeUnit.MILLISECONDS.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			sale2();
		} else {
			try {
				//1 查询库存信息
				String result = stringRedisTemplate.opsForValue().get("inventory001");
				//2 判断库存是否足够
				Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
				//3 扣减库存
				if (inventoryNumber > 0) {
					stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
					retMessage = "成功卖出一个商品，库存剩余: " + inventoryNumber;
					System.out.println(retMessage);
				} else {
					retMessage = "商品卖完了，o(╥﹏╥)o";
				}
			} finally {
				stringRedisTemplate.delete(key);
			}
		}
		return retMessage + "\t" + "服务端口号：" + port;
	}
	
	/**
	 * 分布式锁：setnx + 自旋 + 过期时间防死锁 + 删除校验放误删 + Lua原子性
	 */
	public String sale3() {
		String retMessage = "";
		String key = "SpinRedisLock";
		String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
		
		// setnx <key, value>
		// 原子性设置过期时间，防止宕机后出现死锁
		while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {
			//暂停20毫秒，类似CAS自旋
			try {
				TimeUnit.MILLISECONDS.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		try {
			//1 查询库存信息
			String result = stringRedisTemplate.opsForValue().get("inventory001");
			//2 判断库存是否足够
			Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
			//3 扣减库存
			if (inventoryNumber > 0) {
				stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
				retMessage = "成功卖出一个商品，库存剩余: " + inventoryNumber;
				System.out.println(retMessage);
			} else {
				retMessage = "商品卖完了，o(╥﹏╥)o";
			}
		} finally {
			//stringRedisTemplate.delete(key);
			
			// 添加校验逻辑 防止误删
			//if(stringRedisTemplate.opsForValue().get(key).equalsIgnoreCase(uuidValue)) {
			//	stringRedisTemplate.delete(key);
			//}
			
			// lua 脚本保证原子性
			String delLuaScript = "if (redis.call('get', keys[1]) == args[1]) then return redis.call('del', key[1]) else return 0 end";
			stringRedisTemplate.execute(RedisScript.of(delLuaScript, Boolean.class), Collections.singletonList(key), uuidValue);
		}
		
		return retMessage + "\t" + "服务端口号：" + port;
	}
	
	
	/**
	 * 分布式锁：hash结构 + 自旋 + 过期时间防死锁 + 删除校验放误删 + Lua原子性 + 工厂模式 + 自动续期
	 */
	public String sale4() {
		String msg = "";
		Lock redisLock = distributedLockFactory.getDistributedLock("redis");
		redisLock.lock();
		try {
			String result = stringRedisTemplate.opsForValue().get(Constant.RedisKey.INVENTORY001);
			Integer count = result == null ? 0 : Integer.parseInt(result);
			if (count > 0) {
				count--;
				stringRedisTemplate.opsForValue().set(Constant.RedisKey.INVENTORY001, String.valueOf(count));
				msg = "成功卖出一个商品，库存剩余: " + count + "\t服务端口:" + port;
				System.out.println(msg);
				return msg;
			}
			msg = "商品卖完了，o(╥﹏╥)o" + "\t服务端口:" + port;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			redisLock.unlock();
		}
		return msg;
	}
	
	@Autowired
	private Redisson redisson;
	
	public String saleByRedisson()
	{
		String retMessage = "";
		String key = "zzyyRedisLock";
		RLock redissonLock = redisson.getLock(key);
		redissonLock.lock();
		try
		{
			//1 查询库存信息
			String result = stringRedisTemplate.opsForValue().get("inventory001");
			//2 判断库存是否足够
			Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
			//3 扣减库存
			if(inventoryNumber > 0) {
				stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));
				retMessage = "成功卖出一个商品，库存剩余: "+inventoryNumber;
				System.out.println(retMessage);
			}else{
				retMessage = "商品卖完了，o(╥﹏╥)o";
			}
		}finally {
			if(redissonLock.isLocked() && redissonLock.isHeldByCurrentThread())
			{
				redissonLock.unlock();
			}
		}
		return retMessage+"\t"+"服务端口号："+port;
	}
}